/* 
 * Persistence4J - Simple library for data persistence using java
 * Copyright (c) 2010, Avdhesh yadav.
 * http://www.avdheshyadav.com
 * Contact: avdhesh.yadav@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.avdy.p4j.jdbc.service;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.Validate;

import com.avdy.p4j.common.DAOException;
import com.avdy.p4j.jdbc.model.DTO;
import com.avdy.p4j.jdbc.model.OneToMany;
import com.avdy.p4j.jdbc.transfer.TransferUtil;

/**
 * 
 *  @Author Avdhesh Yadav
 */
public class GenericDaoImpl<E> implements GenericDAO<E>
{
	//
	protected DataFetcher mDataFetcher;
	//batch size 25 is optimal.
	public static final int BATCH_SIZE = 25;


	/**
	 * 
	 * @param dataFetcher DataFetcher
	 */
	public GenericDaoImpl(DataFetcher dataFetcher)
	{
		mDataFetcher = dataFetcher;
	}

	
	/**
	 * 
	 */
	public boolean isEntityExists(E entityObject) throws DAOException
	{
		try 
		{
			GenericDTO gvo  = TransferUtil.getGenericDTO(entityObject);
			return mDataFetcher.isEntityExists(gvo);
		} 
		catch (Exception e) 
		{
			throw new DAOException(e.getMessage());
		}
	}
	

	/**
	 * 
	 */
	public E createEntity(E entityObject) throws DAOException
	{
		try
		{
			GenericDTO gvo  = TransferUtil.getGenericDTO(entityObject);
			mDataFetcher.save(gvo);
			saveAssociations(entityObject);
		} 
		catch (Exception e) 
		{
			throw new DAOException(e.getMessage());
		}
		return entityObject;
	}

	
	/**
	 * 
	 */
	public void saveOrUpdate(E entityObject) throws DAOException
	{
		try
		{
			GenericDTO gvo  = TransferUtil.getGenericDTO(entityObject);
			mDataFetcher.saveOrUpdate(gvo);
			saveAssociations(entityObject);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
			throw new DAOException(e.getMessage());
		}
	}
	

	/**
	 * 
	 */
	public void deleteEntity(E entityObject)	throws DAOException
	{
		try
		{
			GenericDTO geo = TransferUtil.getGenericDTO(entityObject);
			mDataFetcher.deleteEntity(geo);
		} 
		catch (Exception e) 
		{
			throw new DAOException(e.getMessage());
		}
	}


	public E updateEntity(E eo, Map<String,Object> whereClause) throws DAOException
	{
		try
		{
			GenericDTO gvo = TransferUtil.getGenericDTO(eo);
			mDataFetcher.updateEntity(gvo,whereClause);
		} 
		catch (Exception e) 
		{
			throw new DAOException(e.getMessage());
		}
		return eo;
	}


	/**
	 * 
	 */
	public E findByPrimaryKey(Class eoClass, Object[] obj) throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			GenericDTO genericDTO = (GenericDTO)mDataFetcher.findByPrimaryKey(fullTableName, obj);
			if(genericDTO == null)
				return null;
			
			Object entityObject = TransferUtil.getEntityObject(genericDTO);
			readOneToManyFromDatabase(entityObject);
			return (E)entityObject;
		}  
		catch (Exception e)
		{
			e.printStackTrace();
			throw new DAOException(e.getMessage());
		}
	}


	/**
	 * 
	 */
	public List<E> findByAttribute(Class eoClass,String param, String value, String orderByField, String ascDesc,int startPosition, int rows)throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			List<GenericDTO> genericList = (List<GenericDTO>)mDataFetcher.findByAttribute(fullTableName,param, value, orderByField, ascDesc, startPosition, rows);
			List<E> datalist = (List<E>)TransferUtil.getEntityObject(genericList);
			return datalist;
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}


	/**
	 * 
	 */
	public List<E> find(Class eoClass,String[] params,String values[],String orderByField,String ascDesc,int startPosition,int rows) throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			List<GenericDTO> genericList = (List<GenericDTO>)mDataFetcher.find(fullTableName, params, values, orderByField, ascDesc, startPosition, rows);
			List datalist  = (List)TransferUtil.getEntityObject(genericList);
			return datalist;
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}


	/**
	 * 
	 */
	public List<E> findByQuery(Class eoClass, String query, int startPosition, int rows) throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			List<GenericDTO> genericList = (List<GenericDTO>)mDataFetcher.findByQuery(fullTableName, query, startPosition, rows);
			List<E> datalist  = (List<E>)TransferUtil.getEntityObject(genericList);
			return datalist;
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}


	/**
	 * 
	 */
	public List<E> findAll(Class eoClass, int startPosition , int rows) throws DAOException
	{
		try 
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			List<GenericDTO> genericList = (List<GenericDTO>) mDataFetcher.load(fullTableName,startPosition,rows);
			List<E> datalist  = (List<E>)TransferUtil.getEntityObject(genericList);
			return datalist;
		} 
		catch (Exception e) 
		{	
			throw new DAOException(e.getMessage());
		}
	}


	/**
	 * 
	 */
	public int countRows(Class eoClass, String params, String value)throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			return mDataFetcher.countRows(fullTableName, params, value);
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}



	/**
	 * 
	 */
	public void batchInsert(List<E> entityObjects) throws DAOException
	{
		System.out.println("insdie GenericDAOIMPL BatchInsert");
		Validate.notNull(entityObjects, "Entity Objects can not null for Batch Insert");
		if(entityObjects.isEmpty())
		{
			System.out.println("Entity Object List Is Empty.");
			return;
		}

		List<DTO> dtos = new ArrayList<DTO>();
		try
		{
			dtos = TransferUtil.getGenericDTOList(entityObjects);
		}
		catch (Exception e)
		{
			e.printStackTrace();
			throw new DAOException(e.getMessage());
		}

		Map<Integer, List> batches = (Map<Integer, List>)TransferUtil.getBatches(dtos, BATCH_SIZE);

		for (Iterator<Entry<Integer, List>>  itr = batches.entrySet().iterator(); itr.hasNext();)
		{
			Entry<Integer, List> entry = itr.next();
			Integer batchNo = entry.getKey();
			List<DTO> batchList = (List<DTO>)entry.getValue();
			mDataFetcher.batchInsert(batchList);			
		}
	}


	/**
	 * 
	 */
	public List<E> executeQueries(Class eoClass, List<String> queries) throws DAOException
	{
		try
		{
			String fullTableName = TransferUtil.getFullTableNameFromMappings(eoClass.getName());
			Validate.notNull(fullTableName, "Entity is Not registered:" + eoClass.getName());
			List<GenericDTO> genericDTOList = (List<GenericDTO>)mDataFetcher.executeQueries(fullTableName, queries);
			return (List<E>)TransferUtil.getEntityObject(genericDTOList);
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}
	
	
	/**
	 * 
	 */
	public boolean executeQuery(String query) throws DAOException
	{
		try
		{
			return mDataFetcher.executeQuery(query);
		}
		catch (Exception e)
		{
			throw new DAOException(e.getMessage());
		}
	}
	
	
	/**
	 * 
	 * @param entityObject Object
	 * 
	 * @throws Exception
	 */
	private void saveAssociations(Object entityObject) throws Exception
	{
		List<List<GenericDTO>> associatedObjects  = TransferUtil.getAssociations(entityObject);
		for(List<GenericDTO> associatedList : associatedObjects)
		{
			for(GenericDTO genericDTO : associatedList)
			{
				mDataFetcher.saveOrUpdate(genericDTO);
			}
		}
	}
		
	
	/**
	 * Initially we read one-to-many relationships only.
	 */
	private void readOneToManyFromDatabase(Object entityObject) throws Exception
	{
		List<OneToMany> oneToManyList = TransferUtil.getOnetoManyAnnotations(entityObject);
		for(OneToMany oneToMany : oneToManyList)
		{
			if(!oneToMany.isLazy())
			{
				String fullTableName = oneToMany.table();
				String eoClass = TransferUtil.getEoClassFromTable(fullTableName);
				String pkValue = String.valueOf(TransferUtil.getPrimaryKeyObject(entityObject));
				List dataList = findByAttribute(Class.forName(eoClass).newInstance().getClass(), oneToMany.joincolumn(), pkValue, null, null, initialPosition, ALL_ROWS);
				Field field = TransferUtil.getField(entityObject, oneToMany);
				field.setAccessible(true);
				field.set(entityObject, dataList);
			}
		}
	}
}